package spaceinvaders;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class Game extends JPanel implements Runnable, Common { 

	private Dimension dimension;
	private Player player;
	private ArrayList<Alien> aliens;
    private Missile missile;
    private String missileExplosion = "spacepix/explosion.jpg",
    		bombExplosion = "spacepix/explosion.png",
    		bombString = "spacepix/bomb.png",
    		alienExplosion = "spacepix/explosion.png",
    		playerDamage = "spacepix/player_damage.png",
    		playerExplosion = "spacepix/exsplosion.png",
    		backgroundPic = "spacepix/space_background.jpg",
    		gameOverBg = "spacepix/game_over_bg.jpg",
    		welcomePic = "spacepix/startup.jpg"; 
    private ImageIcon missileExplosionImage, bombExplosionImage, bombImage,
    alienExplosionImage, playerDamageImage, playerExplosionImage, welcomeImage,
    	backgroundImage, gameOverBgImage;	
    private int tempShotX = 0, alienX = 150, alienY = 5,
    		alienDirection = -1;
    private AudioInputStream missileInputStream, playerExplosionInputStream,
    alienExplodeInputStream, alienMoveInputStream;
    private Clip missileExplodeClip, playerExplosionClip, alienExplodeClip,
    alienMoveClip;
    private FilePlayer filePlayer;
    
    private int playerHealth, score, level, endGameCount=0;
    		
    private long startTime=System.currentTimeMillis(), beforeTime, timeDiff, sleep;

	private boolean ingame = true, pauseGame = false, inMenu = true, firstTimeThrough = false;

	private Thread animator;
	
	private int LEVELDELAY;
	public static Vector<Integer> directionX;
	public static Vector<Integer> directionY;
	public static int BOTTOM = 100;
	public static int TOP = 5;
	public static  int counter =0;
	public static boolean allDead = true;
	public static boolean invasion = false;

	public Game(int x, int inHealth, int inScore) {
		LEVELDELAY = DELAY - (x%4);
		addKeyListener(new TAdapter());
		//addMouseListener(new ClickMouseListener());
		setFocusable(true);
		dimension = new Dimension(BOARDX, BOARDY);	
		gameInit();
		setDoubleBuffered(true);
		ingame = true;
		playerHealth = inHealth;
		score = inScore;
		level = x;
	}
	

	public void addNotify() {
		super.addNotify();
		gameInit();
	}

	public void gameInit() {
		if(SpaceInvaders.getLevel()>3){
			directionY = new Vector<Integer>(NUM_ALIENS);
			directionX = new Vector<Integer>(NUM_ALIENS);
			Random rand = new Random();
			for(int i =0;i<NUM_ALIENS;i++){
				int sign = rand.nextInt(2);
				if(sign==1){
					directionY.add(rand.nextInt(3)+1);
					directionX.add(rand.nextInt(3)+1);
				}
				else{
					directionY.add(-1*(rand.nextInt(3)+1));
					directionX.add(-1*(rand.nextInt(3)+1));
				}
			}
		}
		player = new Player();
        missile = new Missile();
        bombImage = new ImageIcon(bombString);
        missileExplosionImage = new ImageIcon(missileExplosion);
        bombExplosionImage = new ImageIcon(bombExplosion);
        alienExplosionImage = new ImageIcon(alienExplosion);
        playerDamageImage = new ImageIcon(playerDamage);
        playerExplosionImage = new ImageIcon(playerExplosion);
        welcomeImage = new ImageIcon(welcomePic);
        backgroundImage = new ImageIcon(backgroundPic);
        gameOverBgImage = new ImageIcon(gameOverBg);
        
        try {
			missileExplodeClip = AudioSystem.getClip();
			missileInputStream = AudioSystem.getAudioInputStream(new File("sounds/missile_explode.wav"));
			missileExplodeClip.open(missileInputStream);
			
			playerExplosionClip = AudioSystem.getClip();
			playerExplosionInputStream = AudioSystem.getAudioInputStream(new File("sounds/player_explosion.wav"));
			playerExplosionClip.open(playerExplosionInputStream);
			
			alienExplodeClip = AudioSystem.getClip();
			alienExplodeInputStream = AudioSystem.getAudioInputStream(new File("sounds/alien_explode.wav"));
			alienExplodeClip.open(alienExplodeInputStream);
			
			alienMoveClip = AudioSystem.getClip();
			alienMoveInputStream = AudioSystem.getAudioInputStream(new File("sounds/alien_move.wav"));
			alienMoveClip.open(alienMoveInputStream);
		}
		catch (Exception e1) {
			System.err.println(e1.getMessage());
		}
        
        aliens = new ArrayList<Alien>();

        for (int i=0; i < 5; i++) {
        	for (int j=0; j < 11; j++) {
        		Alien alien = new Alien(alienX + 40*j, alienY + 40*i, i+1, 6-(i+1));
        		aliens.add(alien);
        	}
        }
        
		if (animator == null || !ingame) {
			animator = new Thread(this);
			animator.start();
		}
	}

	public void drawPlayer(Graphics g) {
		if (player.isVisible() || player.getExplosionDelay()<6 || player.getDamageDelay()<6) {
			g.drawImage(player.getImage(), player.getX(), player.getY(), this);
			player.incrementDamageDelay();
			player.incrementExplosionDelay();
			if (player.getExplosionDelay()==6) player.die();
			if (player.getDamageDelay()==6) player.resetImage();
		}
		
		if (player.isDead() && player.getExplosionDelay()==6) {
            player.die();
            ingame = false;
        }
	}
	
	public void drawAliens(Graphics g) 
    {
        Iterator<Alien> alienIterator = aliens.iterator();

        while (alienIterator.hasNext()) {
            Alien alien = (Alien) alienIterator.next();

            if (alien.isVisible() || alien.getExplosionDelay()<6) {
                g.drawImage(alien.getImage(), alien.getX(), alien.getY(), this);
                alien.incrementExplosionDelay();
            }
            
            if (alien.isDead() && alien.getExplosionDelay()==6) {
                alien.die();
            }
        }
    }
	
	public void drawMissile(Graphics g) {
        if (missile.isVisible())
            g.drawImage(missile.getImage(), missile.getX(), missile.getY(), this);
    }
	
	public void drawBombs(Graphics g) {

        Iterator<Alien> alienIterator = aliens.iterator();

        while (alienIterator.hasNext()) {
            Alien alien = (Alien) alienIterator.next();

            Alien.Bomb bomb = alien.getBomb();
            
            if (!bomb.isDestroyed() || bomb.getExplosionDelay()<6) {
                g.drawImage(bomb.getImage(), bomb.getX(), bomb.getY(), this);
                bomb.incrementExplosionDelay();
            }
        }
    }

	
	public void paint(Graphics g) {
		super.paint(g);
		if (ingame) {
			
			if (inMenu && level==1) {
				Insets i = getInsets();
				g.drawImage(welcomeImage.getImage(), i.left, i.top, dimension.width - i.left - i.right, 
			    		dimension.height - i.top - i.bottom, null);
				g.setColor(Color.white);
				Font funky = new Font(Font.DIALOG, Font.BOLD, 14);
		        FontMetrics metr = this.getFontMetrics(funky);
		        g.setFont(funky);
		        
		        g.drawString(STARTGAME, (BOARDX - metr.stringWidth(STARTGAME))/2, 
		        		BOARDY-75);
			}
			else {
				if (firstTimeThrough) {
					pauseGame = true;
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
					firstTimeThrough = false;
					pauseGame = false;
				}
				
				// draw the background
				Insets i = getInsets();
				g.drawImage(backgroundImage.getImage(), i.left, i.top, dimension.width - i.left - i.right, 
						dimension.height - i.top - i.bottom, null);


				// health bar
				g.setColor(Color.green); 
				g.drawString("Health: "+Integer.toString(playerHealth), 5, GROUND+30);
				g.drawString("Score: "+Integer.toString(score), 5, GROUND+45);
				g.drawRect(80, GROUND+20, 100, 10);
				if (playerHealth <= 0)
					g.fillRect(80, GROUND+20, 0, 10);
				else
					g.fillRect(80, GROUND+20, playerHealth, 10);

				drawMissile(g);
				drawBombs(g);
				drawAliens(g);
				drawPlayer(g);

				Font small = new Font("Helvetica", Font.PLAIN, 12);
				FontMetrics metr = this.getFontMetrics(small);

				g.setFont(small);
				g.setColor(Color.white);
				if (!pauseGame)
					g.drawString(PAUSE, BOARDX-metr.stringWidth(PAUSE)-5, 15);
				else g.drawString(RESUME, BOARDX-metr.stringWidth(RESUME)-5, 15);
			}
		}

		Toolkit.getDefaultToolkit().sync();
		g.dispose();
	}

	public void animation()  {
		player.act();

		allDead=true;
		Iterator<Alien> alienIteratorDead = aliens.iterator();
		while (alienIteratorDead.hasNext()) {
			Alien alien = (Alien) alienIteratorDead.next();
			if (alien.isVisible()) {
				allDead = false;
			}
		}
		if(allDead){
			ingame = false;
			//this.setVisible(false);
		}
		
		if (missile.isVisible()) {
			int missileY = missile.getY();
			missileY -= 8;
			if (missileY < 0){
				missile.die();
				score = ((score-25)<=0 ? 0 :score-25);
			}
			else if (missileY <=5 && missileY>=0) {
				missile.setImage(missileExplosionImage.getImage());
				missile.setY(missileY);
				missile.setX(tempShotX-10);
				if (!missileExplodeClip.isRunning()) {
					missileExplodeClip.start();
				}
			}
			else {
				missile.setY(missileY);
				missileExplodeClip.stop();
				missileExplodeClip.setMicrosecondPosition(0);
			}
			
			Iterator<Alien> it = aliens.iterator();
            int missileX = missile.getX();
            int missileY2 = missile.getY();

            while (it.hasNext()) {
            	
                Alien alien = (Alien) it.next();
                int alienX = alien.getX();
                int alienY = alien.getY();

                if (alien.isVisible() && missile.isVisible()) {
                	if (missileX >= (alienX)
                			&& missileX <= (alienX + alien.getWidth())
                			&& missileY2 >= (alienY)
                			&& missileY2 <= (alienY + alien.getHeight()) ) {
                    	alienExplodeClip.stop();
                    	alienExplodeClip.setMicrosecondPosition(0);
                		alien.setDead(true);
                		alien.setImage(alienExplosionImage.getImage());
                		alien.resetExplosionDelay();
                		score+=(alien.getBomb().getCaliber()*100);
                		alienExplodeClip.start();
//                		deaths++;
                		missile.die();
                	}
                }
                

                Alien.Bomb tmpBomb = alien.getBomb();
                int tmpBombX = tmpBomb.getX();
                int tmpBombY = tmpBomb.getY();
                if (!alien.getBomb().isDestroyed() && missile.isVisible()) {
                	if (missileX >= tmpBomb.getX()
                			&& missileX <= (tmpBombX + tmpBomb.getImage().getWidth(null))
                			&& missileY2 >= (tmpBombY)
                			&& missileY2 <= (tmpBombY + tmpBomb.getImage().getHeight(null)) ) {
                		missileExplodeClip.stop();
                		missileExplodeClip.setMicrosecondPosition(0);
                		tmpBomb.setDestroyed(true);
                		tmpBomb.setImage(bombExplosionImage.getImage());
                		tmpBomb.resetExplosionDelay();
                		score+=25;
                		missileExplodeClip.start();
                		missile.die();
                	}
                }
            }
		}
		
		Iterator<Alien> alienIterator = aliens.iterator();
		while (alienIterator.hasNext()) {
			Alien a1 = (Alien) alienIterator.next();
			int x = a1.getX();
			if(SpaceInvaders.getLevel()<=3){
				if (x >= BOARDX - BORDER_RIGHT*2 && alienDirection != -1) {
					alienDirection = -1;
					Iterator<Alien> i1 = aliens.iterator();
					while (i1.hasNext()) {
						Alien a2 = (Alien) i1.next();
						a2.setY(a2.getY() + ALIEN_DOWN);
					}
				}

				if (x <= BORDER_LEFT && alienDirection != 1) {
					alienDirection = 1;

					Iterator<Alien> i2 = aliens.iterator();
					while (i2.hasNext()) {
						Alien a = (Alien)i2.next();
						a.setY(a.getY() + ALIEN_DOWN);
					}
				}
			}
			else{
				LEVELDELAY = DELAY;
				int y = a1.getY();
				if (x >= BOARDX - BORDER_RIGHT*2 && directionX.get(counter)>0 ) {
					directionX.set(counter, directionX.get(counter)*-1);
				}

				if (x <= BORDER_LEFT && directionX.get(counter)<0 ) {
					directionX.set(counter, directionX.get(counter)*-1);
				}
				
				if(y<=TOP && directionY.get(counter)<0){
					directionY.set(counter, directionY.get(counter)*-1);
				}
				
				if(y>=BOTTOM && directionY.get(counter)>0){
					directionY.set(counter, directionY.get(counter)*-1);
					if(BOTTOM<300){
					BOTTOM= BOTTOM +1;
					}
				}
				
			}
			counter ++;
			if (x%40==0)
				a1.toggleImage();
		}
		

		Iterator<Alien> alienIterator2 = aliens.iterator();

		counter =0;
		while (alienIterator2.hasNext()) {
			Alien alien = (Alien) alienIterator2.next();
			
			// if aliens are located too low on the board, game ends
			if (alien.getY() >= (GROUND - 40) && alien.isVisible() == true)
			{			
				allDead = false;
				invasion = true;
				ingame=false;
				break;
			}
			
			
			if (alien.isVisible()) {
				if(SpaceInvaders.getLevel()<=3){
				alien.act(alienDirection);
				}
				else{
					alien.act(counter);
				}
			}
			counter++;
		}
		
		counter=0;
		
		
		Iterator<Alien> alienIterator3 = aliens.iterator();
        Random generator = new Random();

        int index=0, tmpIndex;
        while (alienIterator3.hasNext()) {
        	tmpIndex = index+11;
            int shot = generator.nextInt(25); //decrease for more frequent bombing, etc
            Alien alien = (Alien) alienIterator3.next();
            Alien.Bomb bomb = alien.getBomb();
            if (shot == RAND && alien.isVisible() && bomb.isDestroyed()) {
            	
            	if (tmpIndex<55 && tmpIndex>=44) {
            		if (!aliens.get(tmpIndex).isVisible()) {
            			bomb.setImage(bombImage.getImage());
            			bomb.setDestroyed(false);
            			bomb.setX(alien.getX());
            			bomb.setY(alien.getY());
            		}
            	}
            	
            	else if (tmpIndex<44 && tmpIndex>=33) {
            		if (!aliens.get(tmpIndex).isVisible()) {
            			if (!aliens.get(tmpIndex+11).isVisible()) {
                			bomb.setImage(bombImage.getImage());
            				bomb.setDestroyed(false);
                			bomb.setX(alien.getX());
                			bomb.setY(alien.getY());
            			}
            		}
            	}
            	
            	else if (tmpIndex<33 && tmpIndex>=22) {
            		if (!aliens.get(tmpIndex).isVisible()) {
            			if (!aliens.get(tmpIndex+11).isVisible()) {
            				if (!aliens.get(tmpIndex+22).isVisible()) {
                    			bomb.setImage(bombImage.getImage());
            					bomb.setDestroyed(false);
                    			bomb.setX(alien.getX());
                    			bomb.setY(alien.getY());
            				}
            			}
            		}
            	}
            	
            	else if (tmpIndex<22 && tmpIndex>=11) {
            		if (!aliens.get(tmpIndex).isVisible()) {
            			if (!aliens.get(tmpIndex+11).isVisible()) {
            				if (!aliens.get(tmpIndex+22).isVisible()) {
            					if (!aliens.get(tmpIndex+33).isVisible()) {
                        			bomb.setImage(bombImage.getImage());
            						bomb.setDestroyed(false);
                        			bomb.setX(alien.getX());
                        			bomb.setY(alien.getY());
            					}
            				}
            			}
            		}
            	}
            	
            	else {
        			bomb.setImage(bombImage.getImage());
            		bomb.setDestroyed(false);
        			bomb.setX(alien.getX());
        			bomb.setY(alien.getY());
            	}

            	
            }

            int bombX = bomb.getX();
            int bombY = bomb.getY();
            int playerX = player.getX();
            int playerY = player.getY();

            if (player.isVisible() && !bomb.isDestroyed()) {
            	if ( bombX >= (playerX) && 
            			bombX <= (playerX+PLAYER_WIDTH) &&
            			bombY >= (playerY) && 
            			bombY <= (playerY+PLAYER_HEIGHT) ) {
            		bomb.setDestroyed(true);
            		playerHealth = (playerHealth-(10*bomb.getCaliber())<=0 ? 0 
            				: playerHealth-(10*bomb.getCaliber()));
//            		playerHealth=0;
            		playerExplosionClip.stop();
            		playerExplosionClip.setMicrosecondPosition(0);
            		playerExplosionClip.start();
            		if (playerHealth==0) {
            			player.setDead(true);
            			player.setImage(playerExplosionImage.getImage());
                		player.resetExplosionDelay();
            		}
            		else {
            			player.setImage(playerDamageImage.getImage());
                		player.resetDamageDelay();
            		}
            	}
            }

            if (!bomb.isDestroyed()) {
            	bomb.setY(bomb.getY() + 3);
            	if (bomb.getY()%48 == 0)
            		bomb.toggleImage();
            	// for some reason, the + 10 must be there
            	// otherwise the ship will be bombed by what
            	// seems to be invisible bombs
            	// but the + 10 has its own problems too
                if (bomb.getY() >= GROUND - (BOMBY + 10)) {
                	bomb.setDestroyed(true);
                }
            }
            index++;
        }
	}
	
	public void endGame() {
		player.stopSound();
		alienMoveClip.stop();
		missileExplodeClip.close();
		playerExplosionClip.close();
	    alienMoveClip.close();
	    try {
			filePlayer = new FilePlayer(new File("sounds/spaceinvaders1.wav"));
		} catch (Exception e1) {
			e1.printStackTrace();
		}
	    
		if(allDead) {
			this.setVisible(false);
			SpaceInvaders.score = this.score;
			SpaceInvaders.health = this.playerHealth;
			SpaceInvaders.nextLevel();
			return;
		}
		
		filePlayer.start();
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Graphics g = this.getGraphics();

//		g.setColor(Color.black);
//		g.fillRect(0, 0, BOARDX, BOARDY);

		
		Insets i = getInsets();
		g.drawImage(gameOverBgImage.getImage(), i.left, i.top, dimension.width - i.left - i.right, 
				dimension.height - i.top - i.bottom, null);
		
        g.setColor(new Color(0, 32, 48));
        g.fillRect(200, BOARDY/2 + 35, BOARDX-400, 80);
        g.setColor(Color.white);
        g.drawRect(200, BOARDY/2 + 35, BOARDX-400, 80);
        
        Font gameOver = new Font("Helvetica", Font.BOLD, 50);
        FontMetrics gameOverMetr = this.getFontMetrics(gameOver);

        g.setFont(gameOver);
        
        String whatHappened;
        
        if (invasion) whatHappened = INVASION;
        else whatHappened = GAMEOVER;
        
        g.drawString(whatHappened, (BOARDX - gameOverMetr.stringWidth(whatHappened))/2, 
        		(BOARDY/2)-20);
        
        int finalScore = score-((int) (System.currentTimeMillis()-startTime))/100;
        finalScore = (finalScore<=0 ? 0 : finalScore);

        String finalScoreString = "Final Score: "+finalScore;
        
        g.setColor(Color.white);
        
        Font score = new Font("Helvetica", Font.PLAIN, 20);
        FontMetrics metr1 = this.getFontMetrics(score);
        
        g.setFont(score);
        g.drawString(finalScoreString, ((BOARDX - metr1.stringWidth(finalScoreString))/2), (BOARDY/2)+65);

        Font small = new Font("Helvetica", Font.BOLD, 14);
        FontMetrics metr = this.getFontMetrics(small);

        g.setFont(small);
        g.drawString(RESTART, (BOARDX - metr.stringWidth(RESTART))/2, 
        		(BOARDY/2)+95);
//        restartL = ((BOARDX - metr.stringWidth(RESTART))/2)-(metr.stringWidth(RESTART)/2);
//        restartR = ((BOARDX - metr.stringWidth(RESTART))/2)+metr.stringWidth(RESTART);
//        restartT = (BOARDY/2)+85;
//        restartB = (BOARDY/2)+105;
        if (!allDead) {
        	SpaceInvaders.score = 0;
        	SpaceInvaders.health = 100;
        }
	}

	public void run() {	
		beforeTime = System.currentTimeMillis();
		
		try {
			Thread.sleep(20);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		runWhile();
		
		endGame();
	}
	
	
	private void runWhile() {
		if (level==1) {
			pauseGame = true;
			firstTimeThrough = true;
		}
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		alienMoveClip.loop(Clip.LOOP_CONTINUOUSLY);
		alienMoveClip.start();
		while (ingame) {

			if (!pauseGame) {
				if (!alienMoveClip.isRunning()) {
					alienMoveClip.setMicrosecondPosition(0);
					alienMoveClip.loop(Clip.LOOP_CONTINUOUSLY);
					alienMoveClip.start();
				}
				repaint();
				animation();
			}
			else {
				alienMoveClip.stop();
			}

			timeDiff = System.currentTimeMillis() - beforeTime;
			sleep = LEVELDELAY - timeDiff;

			if (sleep < 0) 
				sleep = 2;
			try {
				Thread.sleep(sleep);
			} catch (InterruptedException e) {
				System.out.println("interrupted");
			}
			beforeTime = System.currentTimeMillis();
		}
	}
	
	
//	private class ClickMouseListener extends MouseAdapter {
//
//		public void mouseClicked(MouseEvent e) {
//			if (!ingame) {
//				int clickX = e.getX(),
//						clickY = e.getY();
//				if (clickX>=restartL && clickX<=restartR
//						&& clickY>=restartT && clickY<=restartB)
//					SpaceInvaders.restartGame();
//			}
//		}
//	}

	private class TAdapter extends KeyAdapter {

		public void keyReleased(KeyEvent e) {
			if (ingame && !pauseGame)
				player.keyReleased(e);
		}

		public void keyPressed(KeyEvent e) {
			if (ingame && !pauseGame)
				player.keyPressed(e);

			int playerX = player.getX();
			int playerY = player.getY();

			int key = e.getKeyCode();
			
			if (ingame && !pauseGame)
			{
				if(key==KeyEvent.VK_N){
					pauseGame=true;

					player.stopSound();
					alienMoveClip.stop();
					missileExplodeClip.close();
				    alienMoveClip.close();
				    
					SpaceInvaders.score = score;
					SpaceInvaders.health = playerHealth;
					SpaceInvaders.nextLevel();
				}
				if (key==KeyEvent.VK_SPACE) {
					if (!missile.isVisible()) {
						try {
							missile = new Missile(playerX+((PLAYER_WIDTH-13)/2), playerY);
							tempShotX = missile.x;
							Clip missileClip = AudioSystem.getClip();
							AudioInputStream missileInputStream = AudioSystem.getAudioInputStream(new File("sounds/missile.wav"));
							missileClip.open(missileInputStream);
							missileClip.start();
						}
						catch (Exception e1) {
							System.err.println(e1.getMessage());
						}
					}
				}
			}
			if (ingame) {
				if (key == KeyEvent.VK_P) {
					pauseGame = (pauseGame ? false : true);
				}
			}
			if (key==KeyEvent.VK_ENTER) {
				inMenu = false;
				pauseGame = false;
			}
			if (!ingame) {
				if (key==KeyEvent.VK_R) {
					filePlayer.stop();
					SpaceInvaders.restartGame();
				}
			}
		}
	}
}